﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Obra.CommandLineInterface;
using Obra.Api;
using Obra.Sync;
using NMock2;
using System.IO;
using Obra.Common;

namespace Obra.Test
{
    [TestFixture]
    public class ObraCommandLineInterfaceTester :
        NMockTester
    {
        private string myPicturesPath;

        [SetUp]
        public override void SetUp()
        {
            base.SetUp();
            this.myPicturesPath = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
        }

        [Test]
        public void UserNameNotSpecified()
        {
            try
            {
                IArgumentParserService argumentParserService = new ArgumentParserServiceProvider();

                Application application = new Application();
                application.AddService(typeof(IArgumentParserService), argumentParserService);

                application.Parse(new string[] {
                    //"-u", "username",
                    "-p", "password",
                });

                Assert.Fail("Did not expect to execute this statement!");
            }
            catch (ArgumentException)
            {
                // Expected
            }
        }

        [Test]
        public void PasswordNotSpecified()
        {
            try
            {
                IArgumentParserService argumentParserService = new ArgumentParserServiceProvider();

                Application application = new Application();
                application.AddService(typeof(IArgumentParserService), argumentParserService);

                application.Parse(new string[] {
                    "-u", "username",
                    //"-p", "password",
                });

                Assert.Fail("Did not expect to execute this statement!");
            }
            catch (ArgumentException)
            {
                // Expected
            }
        }

        [Test]
        public void NonExistentDirectorySpecified()
        {
            try
            {
                IArgumentParserService argumentParserService = new ArgumentParserServiceProvider();

                Application application = new Application();
                application.AddService(typeof(IArgumentParserService), argumentParserService);

                application.Parse(new string[] {
                    "-u", "username",
                    "-p", "password",
                    "-d", @"o:\this\directory\does\not\exist_today_akNEm",
                });

                Assert.Fail("Did not expect to execute this statement!");
            }
            catch (ArgumentException)
            {
                // Expected
            }
        }

        [Test]
        public void DefaultDirectoryIsMyPictures()
        {
            IArgumentParserService argumentParserService = new ArgumentParserServiceProvider();

            Application application = new Application();
            application.AddService(typeof(IArgumentParserService), argumentParserService);

            application.Parse(new string[] {
                    "-u", "username",
                    "-p", "password",
            });

            Assert.AreEqual(
                this.myPicturesPath,
                application.Directory__Test);
        }

        private void SynchronizeNoFiles(
            Application application,
            string directory,
            string[] args)
        {
            IArgumentParserService argumentParseService = new ArgumentParserServiceProvider();
            IMediaService mediaService = this.Mockery.NewMock<IMediaService>();
            IFileSystemService fileSystemService = this.Mockery.NewMock<IFileSystemService>();

            application.AddService(typeof(IArgumentParserService), argumentParseService);
            application.AddService(typeof(IMediaService), mediaService);
            application.AddService(typeof(IFileSystemService), fileSystemService);

            application.Parse(args);

            using (Mockery.Ordered)
            {
                Expect.Once.On(fileSystemService)
                    .Method("GetMediaFiles")
                    .With(directory)
                    .Will(Return.Value(new List<FileInfo>()));

                Expect.Once.On(mediaService)
                    .Method("LogOn")
                    .With("username", "password");

                Expect.Once.On(mediaService)
                    .GetProperty("Albums")
                    .Will(Return.Value(new List<IAlbum>()));

                Expect.Once.On(mediaService)
                    .Method("LogOff");
            }

            application.Execute();
        }

        private Stream GetDummyStream()
        {
            return new MemoryStream();
        }

        private void SynchronizeOneFileDryRun(
            Application application,
            string directory,
            string[] args)
        {
            IArgumentParserService argumentParseService = new ArgumentParserServiceProvider();
            IMediaService mediaService = this.Mockery.NewMock<IMediaService>();
            IFileSystemService fileSystemService = this.Mockery.NewMock<IFileSystemService>();

            application.AddService(typeof(IArgumentParserService), argumentParseService);
            application.AddService(typeof(IMediaService), mediaService);
            application.AddService(typeof(IFileSystemService), fileSystemService);

            application.Parse(args);

            string tempAlbumName = Path.Combine(
                directory,
                Path.GetRandomFileName());

            Directory.CreateDirectory(tempAlbumName);

            string tempMediaName = Path.Combine(
                tempAlbumName,
                Path.GetRandomFileName());

            File.WriteAllText(tempMediaName, "Unit test data.");
            FileInfo tempFileInfo = new FileInfo(tempMediaName);

            using (Mockery.Ordered)
            {
                Expect.Once.On(fileSystemService)
                    .Method("GetMediaFiles")
                    .With(directory)
                    .Will(Return.Value(new List<FileInfo>() { tempFileInfo, }));

                Expect.Once.On(fileSystemService)
                    .Method("ComputeChecksum")
                    .Will(Return.Value("--checksum--"));

                Expect.Once.On(mediaService)
                    .Method("LogOn")
                    .With("username", "password");

                Expect.Once.On(mediaService)
                    .GetProperty("Albums")
                    .Will(Return.Value(new List<IAlbum>()));

                Expect.Once.On(fileSystemService)
                    .Method("LazyLoad")
                    .With(tempMediaName)
                    .Will(Return.Value(new Func<Stream>(this.GetDummyStream)));

                // If this method was not part of a dry run, the following
                // method would be called.

                //Expect.Once.On(mediaService)
                //    .GetProperty("Albums")
                //    .Will(Return.Value(new List<IAlbum>()));

                //Expect.Once.On(mediaService)
                //    .Method("CreateAlbum")
                //    .WithAnyArguments()
                //    .Will(Return.Value(null));

                //Expect.Once.On(mediaService)
                //    .Method("UploadMediaFile")
                //    .WithAnyArguments();

                Expect.Once.On(mediaService)
                    .Method("LogOff");
            }

            application.Execute();
        }

        [Test]
        public void SynchronizeNoFiles()
        {
            Application application = new Application();
            string tempDirectory = TestHelper.CreateTempDirectory();

            string[] args = new string[] {
                "-u", "username",
                "-p", "password",
                "-d", tempDirectory,
            };

            this.SynchronizeNoFiles(
                application,
                tempDirectory,
                args);

            string localSyncFileName = Path.Combine(
                tempDirectory,
                "local.obr");

            string remoteSyncFileName = Path.Combine(
                tempDirectory,
                "remote.obr");

            Assert.IsTrue(File.Exists(localSyncFileName));
            Assert.IsTrue(File.Exists(remoteSyncFileName));

            Assert.AreEqual(
                localSyncFileName,
                application.LocalFileName__Test);

            Assert.AreEqual(
                remoteSyncFileName,
                application.RemoteFileName__Test);
        }

        [Test]
        public void DryRunFirstTime()
        {
            Application application = new Application();
            string tempDirectory = TestHelper.CreateTempDirectory();

            string[] args = new string[] {
                "-u", "username",
                "-p", "password",
                "-d", tempDirectory,
                "--dry-run",
            };

            this.SynchronizeNoFiles(
                application,
                tempDirectory,
                args);

            string localSyncFileName = Path.Combine(
                tempDirectory,
                "local.obr");

            string remoteSyncFileName = Path.Combine(
                tempDirectory,
                "remote.obr");

            // Because this is the first run, the sync files are never created.
            Assert.IsFalse(File.Exists(localSyncFileName));
            Assert.IsFalse(File.Exists(remoteSyncFileName));

            Assert.AreNotEqual(
                localSyncFileName,
                application.LocalFileName__Test);

            Assert.AreNotEqual(
                remoteSyncFileName,
                application.RemoteFileName__Test);
        }

        /// <summary>
        /// Dry run should not modify the contents of local.obr or remote.obr.
        /// </summary>
        [Test]
        public void DryRunSecondTime()
        {
            Application application1 = new Application();
            string tempDirectory = TestHelper.CreateTempDirectory();

            string localFileName = Path.Combine(tempDirectory, "local.obr");
            string remoteFileName = Path.Combine(tempDirectory, "remote.obr");

            string[] args1 = new string[] {
                "-u", "username",
                "-p", "password",
                "-d", tempDirectory,
            };

            this.SynchronizeNoFiles(
                application1,
                tempDirectory,
                args1);

            string localFileChecksum1 = Checksum.ComputeMD5(new FileStream(
                localFileName, 
                FileMode.Open));

            string remoteFileChecksum1 = Checksum.ComputeMD5(new FileStream(
                remoteFileName,
                FileMode.Open));

            Application application2 = new Application();

            string[] args2 = new string[] {
                "-u", "username",
                "-p", "password",
                "-d", tempDirectory,
                "--dry-run",
            };

            this.SynchronizeOneFileDryRun(
                application2,
                tempDirectory,
                args2);

            string localFileChecksum1_DryRun = Checksum.ComputeMD5(new FileStream(
                localFileName,
                FileMode.Open));

            string remoteFileChecksum1_DryRun = Checksum.ComputeMD5(new FileStream(
                remoteFileName,
                FileMode.Open));

            string localFileChecksum2 = Checksum.ComputeMD5(new FileStream(
                application2.LocalFileName__Test,
                FileMode.Open));

            string remoteFileChecksum2 = Checksum.ComputeMD5(new FileStream(
                application2.RemoteFileName__Test,
                FileMode.Open));

            Assert.AreEqual(localFileChecksum1, localFileChecksum1_DryRun);
            Assert.AreEqual(remoteFileChecksum1, remoteFileChecksum1_DryRun);

            Assert.AreNotEqual(localFileChecksum1, localFileChecksum2);
            Assert.AreNotEqual(remoteFileChecksum1, remoteFileChecksum2);
        }
    }
}
